﻿#pragma once

namespace algorithm4cpp
{
	/************************************************************************/
	/* 
	Convert Chinese characters in string to pinyin.
	Polyphones supported!
	*/
	/************************************************************************/
	class pinyin_util
	{
	public:
		typedef set<wchar_t>					shengmu_set;
		typedef set<wstring>					pinyin_set;
		typedef hash_map<wchar_t, pinyin_set>	char_pinyin_map;
	public:
		pinyin_util(void);
		~pinyin_util(void);

	public:
		static pinyin_set chinese_to_pinyin( const wstring & source ) 
		{
			pinyin_set result;
			result.insert( L"" );

			for ( size_t index = 0; index < source.length(); ++index ) {
				pinyin_set pinyin = chinese_to_pinyin( source[index] );
				if ( pinyin.empty() ) {
					result = append_set( result, source[index] );
				} else {
					result = append_set( result, pinyin );
				}
			}
			return result;
		}

		static pinyin_set chinese_to_shengmu( const wstring & source ) 
		{
			pinyin_set result;
			result.insert( L"" );

			for ( size_t index = 0; index < source.length(); ++index ) {
				shengmu_set shengmu = chinese_to_shengmu( source[index] );
				if ( shengmu.empty() ) {
					result = append_set( result, source[index] );
				} else {
					result = append_set( result, shengmu );
				}
			}
			return result;
		}

		//************************************
		// Method:    is_chinese, see http://unicode.org/charts/PDF/U4E00.pdf
		// Chinese, that it CJK Unified Ideographs, range: 4E00~9FCF
		// CJK Unified Ideographs extensions and other CJK codes not supported.
		// CJK Unified Ideographs Extension A range: 3400~4DBF
		// CJK Unified Ideographs Extension B range: 20000~2A6DF
		// CJK Unified Ideographs Extension C range: 2A700~2B73F
		// CJK Compatibility Ideographs range: F900~FAFF
		// CJK Compatibility Ideographs Supplement range: 2F800~2FA1F
		// FullName:  pinyin_util::is_chinese
		// Access:    public static 
		// Returns:   bool
		// Qualifier:
		// Parameter: wchar_t c
		//************************************
		static bool is_chinese( wchar_t c )
		{
			if ( c < CHINESE_START ) {
				return false;
			}
			if ( c > CHINESE_END ) {
				return false;
			}

			return true;
		}

		//************************************
		// Method:    chinese_to_pinyin
		// FullName:  algorithm4cpp::pinyin_util::chinese_to_pinyin
		// Access:    public static 
		// Returns:   set<wstring>, Chinese pinyin, empty if not found
		// Qualifier:
		// Parameter: wchar_t c, Chinese character to lookup.
		//************************************
		static pinyin_set chinese_to_pinyin( wchar_t c )
		{
			if ( ! is_chinese(c ) ) {
				return pinyin_set();
			}

			pinyin_set pinyin = char_to_pinyin( c );
			if ( ! pinyin.empty() ) {
				return pinyin;
			}

			short asc = chinese_asc( c );
			if ( asc == 0 ) {
				return pinyin_set();
			}

			wstring gb_pinyin = asc_to_pinyin(asc);
			if ( gb_pinyin != L"" ) {
				pinyin.insert( gb_pinyin );
			}

			return pinyin;
		}

		//************************************
		// Method:    chinese_to_shengmu
		// FullName:  algorithm4cpp::pinyin_util::chinese_to_shengmu
		// Access:    public static 
		// Returns:   set<wchar_t>, set of Chinese shengmu, empty if not found pinyin
		// Qualifier:
		// Parameter: wchar_t c, Chinese character to lookup
		//************************************
		static shengmu_set chinese_to_shengmu( wchar_t c )
		{
			shengmu_set shengmu;

			pinyin_set pinyin = chinese_to_pinyin(c);
			for ( pinyin_set::const_iterator it = pinyin.begin(); it != pinyin.end(); ++it ) {
				if ( it->length() == 0 ) {
					assert( false );
					continue;
				}
				shengmu.insert( it->at(0) );
			}

			return shengmu;
		}

	private:
		static pinyin_set append_set( const pinyin_set & pinyin, wchar_t c )
		{
			pinyin_set result;
			BOOST_FOREACH( const wstring & p, pinyin )
			{
				result.insert( p + c );
			}
			return result;
		}

		template<typename T, typename Y>
		static set<T> append_set( const set<T> & pinyin, const set<Y> & to_append )
		{
			set<T> result;
			BOOST_FOREACH( const T & p, pinyin )
			{
				BOOST_FOREACH( const Y & a, to_append )
				{
					result.insert( p + a );
				}
			}
			return result;
		}

		static short chinese_asc( wchar_t c );

		static wstring asc_to_pinyin( short i );

		static bool init();

		static pinyin_set char_to_pinyin( wchar_t c )
		{
			char_pinyin_map::const_iterator it = character_to_pinyin_.find( c );
			if ( it == character_to_pinyin_.end() ) {
				return pinyin_set();
			}

			return it->second;
		}

	public:
		const static wchar_t	CHINESE_START	= 0x4e00;	// L'一';
		const static wchar_t	CHINESE_END		= 0x9fff;		//

	private:
		const static size_t pinyin_count = 396;
		const static short chinese_bounces[pinyin_count];
		const static wstring pinyins[pinyin_count];

		static char_pinyin_map character_to_pinyin_;
		const static bool init_;
	};
}
